package edu.hawaii.duedates;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.cli.HelpFormatter;

/**
 * Parse command line args for DueDates options.
 * 
 * @author John Ancheta Daniel Arakaki Mari-Lee Flestado
 * @version 1.0
 */
public class CommandLineArgs {

  /** -uhm command line option. */
  public static final String UHM_OPTION = "uhm";
  /** -hsl command line option. */
  public static final String HSL_OPTION = "hsl";
  /** -sort command line option. */
  public static final String SORT_OPTION = "sort";
  /** -within command line option. */
  public static final String WITHIN_OPTION = "within";
  /** -wakeup command line option. */
  public static final String WAKEUP_OPTION = "wakeup";
  /** -email command line option. */
  public static final String EMAIL_OPTION = "email";
  /** -console command line option. */
  public static final String CONSOLE_OPTION = "console";

  /**
   * CommandLineArgs default constructor.
   */
  public CommandLineArgs() {
    options.addOption("v", "verbose", false,
        "Shows exception stack trace for errors.");

    options.addOption(CONSOLE_OPTION, false,
        "The report is sent to the console.");

    Option sort = new Option(SORT_OPTION, true,
        "Sorts by library or duedates.");
    sort.setArgs(1);
    sort.setArgName("library|duedate");
    options.addOption(sort);

    Option within = new Option(WITHIN_OPTION, true,
        "Get books which are due within ndays from duedate.");
    within.setArgs(1);
    within.setArgName("ndays");
    options.addOption(within);

    Option wakeup = new Option(WAKEUP_OPTION, true,
        "<interval> The number of days between checks.\n"
            + "For <interval> = 0, run the system once.");
    wakeup.setArgs(1);
    wakeup.setType(new Double(0));
    wakeup.setArgName("interval");
    options.addOption(wakeup);

    Option uhm = new Option(
        UHM_OPTION,
        false,
        "Site options for University of Hawaii at Manoa library.\n"
            + "<id> is the UH id number in 1234-5678 or 12345678 format.\n"
            + "<lastname> is the last name of the user");
    uhm.setArgs(2);
    uhm.setArgName("id> <lastname");
    options.addOption(uhm);

    Option email = new Option(EMAIL_OPTION, true,
        "Email due dates information.\n"
            + "<emailaccount> is a valid email address.\n"
            + "<smtpserver> is smtp server to send the email.\n");
    email.setArgs(2);
    email.setArgName("emailaccount> <smtpserver");
    options.addOption(email);

    Option hsl = new Option(HSL_OPTION, false,
        "Site options for Hawaii State Library(HSL)\n"
            + "<id> is the HSL id number\n"
            + "<last4digitsoftelephonenumber>"
            + " is the last four digits of phone number.");
    hsl.setArgs(2);
    hsl.setArgName("id> <last4digitsoftelephonenumber");
    options.addOption(hsl);
  }

  /**
   * Is s a negative number?
   * 
   * @param s
   *            String to check.
   * @return true if negative number, otherwise false.
   */
  private boolean isNegativeNumber(String s) {
    try {
      double value = Double.parseDouble(s);
      if (value < 0) {
        return true;
      }
      return false;
    } 
    catch (NumberFormatException e) {
      return false;
    }
  }
  
  /**
   * Verifies argument to option is non negative.
   * @param option The option to verify.
   * @param args The command line args.
   * @throws ParseException Thrown if the option is negative.
   */
  private void verifyOptionArgument(String option, String[] args) throws ParseException {
    if (args == null) {
      throw new ParseException("No options found.");
    }
    for (int i = 0; i < args.length; i++) {
      if (option.equals(args[i]) && i + 1 < args.length
          && isNegativeNumber(args[i + 1])) {
        throw new ParseException("Argument for " + option + " \""
            + args[i + 1] + "\" should not be negative.");
      }
    }
  }

  /**
   * Parse command line for DueDates options.
   * 
   * @param args
   *            From the command line.
   * 
   * @throws ParseException
   *             If found problem.
   */
  public void parse(String[] args) throws ParseException {
    verifyOptionArgument("-wakeup", args);
    verifyOptionArgument("-within", args);
    CommandLineParser parser = new PosixParser();
    commandLine = parser.parse(options, args);

    if (!this.hasConsoleOption() && !this.hasEmailOption()) {
      throw new ParseException("Must use \"-console\" and/or "
          + "\"-email <emailaccount> <smtpserver>\" options.");
    }

    checkSortOption();
    checkWakeupOption();
    checkWithinOption();

    if (!this.hasUhmOption() && !this.hasHslOption()) {
      throw new ParseException("No -uhm or -hsl used.");
    }

    checkExtraArgs();
  }
  
  /**
   * Checks the -sort argument is either duedate or library.
   * @throws ParseException Option is invalid.
   */
  private void checkSortOption() throws ParseException {
    if (this.hasSortOption()) {
      String value = commandLine.getOptionValues(SORT_OPTION)[0];
      if (!"duedate".equals(value) && !"library".equals(value)) {
        throw new ParseException("Invalid value \"" + value
                + "\" used with -sort.");
      }
    }
  }
  
  /**
   * Checks if -within value is valid.
   * @throws ParseException Option is not valid.
   */
  private void checkWithinOption() throws ParseException {
    if (this.hasWithinOption()) {
      String value = commandLine.getOptionValues(WITHIN_OPTION)[0];
      boolean valid = true;
      try {
        Integer.parseInt(value);
      } 
      catch (NumberFormatException e) {
        valid = false;
      }
      if (!valid) {
        throw new ParseException("Invalid value \"" + value
                  + "\" used with -within.");
      }
    }
  }
  /**
   * Checks if the -wakeup value is valid.
   * @throws ParseException Option is not valid.
   */
  private void checkWakeupOption() throws ParseException {
    if (this.hasWakeupOption()) {
      String value = commandLine.getOptionValues(WAKEUP_OPTION)[0];
      boolean valid = true;
      try {
        Double.parseDouble(value);
      } 
      catch (NumberFormatException e) {
        valid = false;
      }
      if (!valid) {
        throw new ParseException("Invalid value \"" + value
                      + "\" used with -wakeup.");
      }
    }
  }
  /**
   * Checks if there are extra unused options.
   * @throws ParseException Thrown if there is.
   */
  private void checkExtraArgs() throws ParseException {
    String[] extras = commandLine.getArgs();
    if (extras.length > 0) {
      StringBuffer total = new StringBuffer("Unknown arguments: ");
      for (String s : extras) {
        total.append("\"" + s + "\" ");
      }
      throw new ParseException(total.toString());
    } 
  }
  /**
   * Check to see if -verbose option was used.
   * 
   * @return true if used, false otherwise.
   */
  public boolean hasVerboseOption() {
    return commandLine.hasOption("verbose");
  }

  /**
   * Checks if -uhm option was used.
   * 
   * @return true if used, false if not used
   */
  public boolean hasUhmOption() {
    return commandLine.hasOption(UHM_OPTION);
  }

  /**
   * Get the value of -uhm id option.
   * 
   * @return Value of -uhm id option.
   */
  public String getUhmId() {
    String[] values = commandLine.getOptionValues(UHM_OPTION);
    return values[0];
  }

  /**
   * Get the value of -uhm lastName option.
   * 
   * @return Value of -uhm lastName.
   */
  public String getUhmLastName() {
    String[] values = commandLine.getOptionValues(UHM_OPTION);
    return values[1];
  }

  /**
   * Checks if -within option was used.
   * 
   * @return true if -within used, false if -within not used
   */
  public boolean hasHslOption() {
    return commandLine.hasOption(HSL_OPTION);
  }

  /**
   * Get option1 of -hsl option.
   * 
   * @return The value of option1 for -hsl.
   */
  public String getHslId() {
    String[] values = commandLine.getOptionValues(HSL_OPTION);
    return values[0];
  }

  /**
   * Get option2 of -hsl option.
   * 
   * @return The value of option2 for -hsl.
   */
  public String getHslLastFourDigitsForPhoneNumber() {
    String[] values = commandLine.getOptionValues(HSL_OPTION);
    return values[1];
  }

  /**
   * Checks if -sort option was used.
   * 
   * @return true if -sort used, otherwise false
   */
  public boolean hasSortOption() {
    return commandLine.hasOption(SORT_OPTION);
  }

  /**
   * Get the -sort value.
   * 
   * @return either book's due date or library (default: library)
   */
  public String getSortValue() {
    if (!this.hasSortOption()) {
      return "library";
    }
    String[] values = commandLine.getOptionValues(SORT_OPTION);
    return values[0];
  }

  /**
   * Checks if -within was used.
   * 
   * @return true if -within used, otherwise false
   */
  public boolean hasWithinOption() {
    return commandLine.hasOption(WITHIN_OPTION);
  }

  /**
   * Get the value of -within option.
   * 
   * @return Value of -within option (default: 0).
   */
  public int getWithinValue() {
    if (!this.hasWithinOption()) {
      return 0;
    }
    String[] values = commandLine.getOptionValues(WITHIN_OPTION);
    return Integer.parseInt(values[0]);
  }

  /**
   * Checks if -wakeup was used.
   * 
   * @return true if -within used, otherwise false
   */
  public boolean hasWakeupOption() {
    return commandLine.hasOption(WAKEUP_OPTION);
  }

  /**
   * Get the value of -wakeup option.
   * 
   * @return Value of -wakeup option (default: 0).
   */
  public double getWakeupValue() {
    if (!this.hasWakeupOption()) {
      return 0;
    }
    String[] values = commandLine.getOptionValues(WAKEUP_OPTION);
    return Double.parseDouble(values[0]);
  }

  /**
   * Checks if -email was used.
   * 
   * @return true if -email used, otherwise false
   */
  public boolean hasEmailOption() {
    return commandLine.hasOption(EMAIL_OPTION);
  }

  /**
   * Get the first argument of -email option.
   * 
   * @return Value of -email option.
   */
  public String getEmailAddressValue() {
    if (!this.hasEmailOption()) {
      return null;
    }
    String[] values = commandLine.getOptionValues(EMAIL_OPTION);
    return values[0];
  }

  /**
   * Get the first argument of -email option.
   * 
   * @return Value of -email option.
   */
  public String getEmailSmtpServerValue() {
    if (!this.hasEmailOption()) {
      return null;
    }
    String[] values = commandLine.getOptionValues(EMAIL_OPTION);
    return values[1];
  }

  /**
   * Checks if -console was used.
   * 
   * @return true if -console used, otherwise false
   */
  public boolean hasConsoleOption() {
    return commandLine.hasOption(CONSOLE_OPTION);
  }

  /**
   * Print the command line help information.
   */
  public void printHelp() {
    HelpFormatter formatter = new HelpFormatter();
    formatter
        .printHelp(
            "java -jar DueDates.jar"
                + " [ -verbose ]"
                + " [ -console | -email <emailaccount> <smtpserver> ]"
                + " [ -sort [ library | duedate ]]"
                + " [ -within <days> ]"
                + " [ -wakeup <interval> ]"
                + " [ -uhm <id> <lastname> | -hsl <id> <last4digitsoftelephonenumber> ]",
            options);
  }

  private CommandLine commandLine = null;
  private final Options options = new Options();
}
